#include <libbase/dswtcpclient_base.h>
#include <sys/uio.h>

CDswtcpclient_base::CDswtcpclient_base(LPST_ENV_T pEnv, struct event_base *base, int nfd, int nWantReadLen)
: CDswbase(pEnv)
, m_nWantReadLen(nWantReadLen)
, m_nPayloadLen(0)
, m_pRxBuffer(NULL)
{
	m_pRxBuffer = new char[RXBUF_SIZE];
	
	m_pBev = bufferevent_socket_new(base, nfd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE);
	struct timeval tv = {30, 0};
	bufferevent_set_timeouts(m_pBev, &tv, NULL);
	m_nConnectTask.ev = m_nDNSRequestTask.ev = NULL;
	OnConnect(m_pBev, BEV_EVENT_CONNECTED);
}

CDswtcpclient_base::CDswtcpclient_base(LPST_ENV_T pEnv, char* pHost, int nPort)
: CDswbase(pEnv)
, m_pBev(NULL)
, m_nPort(nPort)
, m_nPayloadLen(0)
, m_nWantReadLen(0)
, m_pRxBuffer(NULL)
{
	m_pRxBuffer = new char[RXBUF_SIZE];
	strcpy(m_sHost, pHost);
	memset(m_sIpAddr, 0, sizeof(m_sIpAddr));
	m_nConnectTask.ev = NULL;
	CREATE_EV(m_nDNSRequestTask, 1, 0, m_pEnv->pEvBase, CDswtcpclient_base::cbDNSRequestTaskFunc, this);
}

CDswtcpclient_base::~CDswtcpclient_base()
{
	CLEAR_EV(m_nDNSRequestTask);
	CLEAR_EV(m_nConnectTask);
	if (m_pBev){
		bufferevent_disable(m_pBev, EV_READ | EV_WRITE);
		bufferevent_free(m_pBev);
	}
	m_pBev = NULL;
	if (m_pRxBuffer)
		delete m_pRxBuffer;
}

void CDswtcpclient_base::DNSRequestTaskFunc(int fd, short events)
{
	struct evutil_addrinfo hints;
	struct evdns_getaddrinfo_request *req;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_flags = EVUTIL_AI_CANONNAME;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	req = evdns_getaddrinfo(m_pEnv->pDns, m_sHost, NULL , &hints, CDswtcpclient_base::cbOnDnsRequest, (void*)this);
	if (req == NULL)
		LOG4CPLUS_DEBUG(log_dsw, "Request for " << m_sHost << " returned immediately!");
}

void CDswtcpclient_base::OnDnsRequest(int errcode, struct evutil_addrinfo *addr)
{
	if (errcode){
		 LOG4CPLUS_ERROR(log_dsw, "Dns request error! err = " << evutil_gai_strerror(errcode));
		 TRIG_EV(m_nDNSRequestTask, 5, 0);
	}
	else{
		struct evutil_addrinfo *ai;
		char ip[128];
		LOG4CPLUS_DEBUG(log_dsw, "Dns resolved, hostname = [ " << m_sHost << " ]");
		for (ai = addr; ai; ai = ai->ai_next){
			const char *s = NULL;
			if (ai->ai_family == AF_INET){
				struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;
				s = evutil_inet_ntop(AF_INET, &sin->sin_addr, ip, 128);
				LOG4CPLUS_DEBUG(log_dsw, "DNS Request ip = [ " << s << " ]");
			}
			if(s){
				strcpy(m_sIpAddr, s);
				if (m_nConnectTask.ev){
					TRIG_EV(m_nConnectTask, 1, 0);
				}
				else{
					CREATE_EV(m_nConnectTask, 1, 0, m_pEnv->pEvBase, CDswtcpclient_base::cbConnectTaskFunc, this);
				}
				break;
			}
		}
		 if (addr)
		 	evutil_freeaddrinfo(addr);
	}
}

void CDswtcpclient_base::ConnectTaskFunc(int fd, short events)
{
	if (!m_pBev)
		m_pBev = bufferevent_socket_new(m_pEnv->pEvBase, -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE);
	struct timeval tv = {30, 0};
	bufferevent_set_timeouts(m_pBev, &tv, NULL);
	bufferevent_setcb(m_pBev, NULL, NULL, CDswtcpclient_base::cb_OnEvent, this);
	LOG4CPLUS_DEBUG(log_dsw, "Connect to host [ " << m_sIpAddr << ":" << m_nPort << " ]");
	bufferevent_socket_connect_hostname(m_pBev, m_pEnv->pDns, AF_UNSPEC, m_sIpAddr, m_nPort);
}

void CDswtcpclient_base::ReConnect(int nSecond, int nUSecond)
{
	LOG4CPLUS_DEBUG(log_dsw, "Reconnect " << m_sHost << ":" << m_nPort << " after " << nSecond << " Sec!");
	if (m_pBev){
		bufferevent_disable(m_pBev, EV_READ | EV_WRITE);
		bufferevent_free(m_pBev);
	}
	m_pBev = NULL;
	if (m_nDNSRequestTask.ev){
		TRIG_EV(m_nDNSRequestTask, nSecond, nUSecond);
	}
	else{
		CREATE_EV(m_nDNSRequestTask, 1, 0, m_pEnv->pEvBase, CDswtcpclient_base::cbDNSRequestTaskFunc, this);
	}
}

void CDswtcpclient_base::OnConnect(struct bufferevent *bev, short events)
{
	m_nPayloadLen = 0;
	bufferevent_setwatermark(bev, EV_READ, m_nWantReadLen, m_nWantReadLen);
	bufferevent_setcb(bev, CDswtcpclient_base::cb_OnRead, CDswtcpclient_base::cb_OnWrite, CDswtcpclient_base::cb_OnEvent, this);
	bufferevent_enable(bev, EV_READ | EV_WRITE);
}

void CDswtcpclient_base::OnEvent(struct bufferevent *bev, short events)
{
	if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)){
		if ((EVUTIL_SOCKET_ERROR() == EAGAIN) || (EVUTIL_SOCKET_ERROR() == EINTR)){
			LOG4CPLUS_DEBUG(log_dsw, "Low speed socket, Err = " << evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
			OnError(NULL, events);
			return;
		}
		else{
			LOG4CPLUS_ERROR(log_dsw, "Error from bufferevent! err = " << evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())
				 << " ,errno = " << EVUTIL_SOCKET_ERROR());
			OnError(NULL, events);
		}
	}
	else if (events & BEV_EVENT_CONNECTED){
		LOG4CPLUS_DEBUG(log_dsw, "Connect server [ " << m_sIpAddr << ":" << m_nPort << " ] ok!");
		OnConnect(bev, events);
	}
}

void CDswtcpclient_base::OnRead(struct bufferevent *bev)
{
	 struct evbuffer *input = bufferevent_get_input(bev);
	 int nLen = (int)evbuffer_get_length(input);
	 int j;
	 
	 if ((nLen >= m_nWantReadLen) && (m_nPayloadLen == 0)){
		bufferevent_read(bev, m_pRxBuffer, m_nWantReadLen);
		m_nPayloadLen = MakePayloadLength(m_pRxBuffer, m_nWantReadLen);
		if (m_nPayloadLen >= RXBUF_SIZE){
			LOG4CPLUS_ERROR(log_dsw, "The data length is too large, this socket is invalid and close it soon!");
			OnError(bev, BEV_EVENT_ERROR);
			return;
		}
		else{
			if (m_nPayloadLen > 0){
				bufferevent_setwatermark(bev, EV_READ, m_nPayloadLen, m_nPayloadLen);
			}
			else{
				if (!ProcessMessage(NULL, 0, bufferevent_get_output(bev))){
					LOG4CPLUS_ERROR(log_dsw, "Invalid recved data, this socket is invalid and close it soon!");
					OnError(bev, BEV_EVENT_ERROR);
					return;
				}
				else
					bufferevent_setwatermark(bev, EV_READ, m_nWantReadLen, m_nWantReadLen);
			}
		}
	}
	else if ((m_nPayloadLen > 0) && (nLen >= m_nPayloadLen)){
		bufferevent_read(bev, m_pRxBuffer, m_nPayloadLen);
		m_pRxBuffer[m_nPayloadLen] = '\0';
		if (!ProcessMessage(m_pRxBuffer, m_nPayloadLen, bufferevent_get_output(bev))){
			LOG4CPLUS_ERROR(log_dsw, "Invalid recved data, this socket is invalid and close it soon!");
			OnError(bev, BEV_EVENT_ERROR);
			return;
		}
		else
			bufferevent_setwatermark(bev, EV_READ, m_nWantReadLen, m_nWantReadLen);
		m_nPayloadLen = 0;
	 }
}

